<?php
/**
 * @table paysystems
 * @id 1shoppingcart
 * @title 1ShoppingCart/MCSSL
 * @visible_link http://www.1shoppingcart.com/
 * @hidden_link http://OneShoppingCart.evyy.net/c/34228/32823/1153
 * @recurring paysystem
 * @logo_url 1shopingcart.png
 * @country US
 * @fixed_products 1
 * @am_payment_api 6.0
 */
class Am_Paysystem_1shoppingcart extends Am_Paysystem_Abstract
{
    const PLUGIN_STATUS = self::STATUS_PRODUCTION;
    const PLUGIN_REVISION = '6.3.32';

    const URL = "http://www.marketerschoice.com/app/javanof.asp";

    protected $defaultTitle = '1ShoppingCart';
    protected $defaultDescription = 'All major credit cards accepted';

    function getSupportedCurrencies()
    {
        return ['USD', 'EUR', 'AUD', 'GBP'];
    }
    public function canAutoCreate()
    {
        return true;
    }

    public function _initSetupForm(Am_Form_Setup $form)
    {
        $form->addInteger('merchant_id', ['size' => 20])
            ->setLabel('Your Merchant ID#');

        $form->addSecretText('password')->setLabel("Postback Password\n" .
            'Should be the same as in your 1SC account');

        $form->addSecretText('key', ['size' => 30])
            ->setLabel("API Key\n" .
                '1SC -> My Account -> API Settings -> Your Current Merchant API Key');

        $form->addAdvCheckbox('skip_amount_check')
            ->setLabel("Skip Amount Check\n" .
            'Plugin will not check amount of incomming transaction. This option makes it possible to use coupons on 1SC');
        $form->addText('api_resend', ['size' => 60])
            ->setLabel("Resend API Requests\n" .
                'Specify url of third-party script that should receive API notifications as well');
    }

    public function init()
    {
        parent::init();
        $this->getDi()->billingPlanTable->customFields()
            ->add(new Am_CustomFieldText('1shoppingcart_id', "1ShoppingCart Product#",
                    "for any products you have to create corresponding product in 1SC
                admin panel and enter the id# here"));
    }

    public function _process($invoice, $request, $result)
    {
        $a = new Am_Paysystem_Action_Redirect(self::URL);
        $result->setAction($a);
        $a->MerchantID = $this->config['merchant_id'];
        $a->ProductID = $invoice->getItem(0)->getBillingPlanData('1shoppingcart_id');
        $a->AMemberID = $invoice->invoice_id;
        $a->PostBackURL = $this->getDi()->surl("payment/1shoppingcart/ipn", false);
        $a->clear = 1;
        $a->filterEmpty();
        $result->setAction($a);
    }

    public function directAction($request, $response, array $invokeArgs)
    {
        if($request->getActionName() == 'api' && $api_resend = $this->getConfig('api_resend')){
            try{

                $client = new Am_HttpRequest($api_resend, Am_HttpRequest::METHOD_POST);
                $client->setHeader('Content-type', 'text/xml');
                $client->setBody($request->getRawBody());
                $response = $client->send();
            } catch(Exception $e) {
                $this->getDi()->logger->error("Error in 1sc plugin", ["exception" => $e]);
            }
        }
        parent::directAction($request, $response, $invokeArgs);
    }

    public function createTransaction($request, $response, array $invokeArgs)
    {
        if ($request->getActionName() == 'api')
            return new Am_Paysystem_Transaction_1shoppingcart_api($this, $request, $response, $invokeArgs);
        else
            return new Am_Paysystem_Transaction_1shoppingcart($this, $request, $response, $invokeArgs);
    }

    public function getRecurringType()
    {
        return self::REPORTS_REBILL;
    }


    function findOrder($order_id)
    {
        return $this->getDi()->invoicePaymentTable->findFirstBy(['transaction_id' => $order_id, 'paysys_id' => $this->getId()]);
    }
}

class Am_Paysystem_Transaction_1shoppingcart_api extends Am_Paysystem_Transaction_Incoming
{
    protected $_xml;
    protected $_order;
    protected $_client;
    const APIURL = 'https://www.mcssl.com/API/';

    function __construct(Am_Paysystem_Abstract $plugin, Am_Mvc_Request $request, Am_Mvc_Response $response, $invokeArgs)
    {
        parent::__construct($plugin, $request, $response, $invokeArgs);
        $str = $request->getRawBody();
        $this->_xml = simplexml_load_string($str);
        if (!$this->_xml)
            throw new Am_Exception_Paysystem_TransactionInvalid('Invalid xml received from 1SC: ' . $str);

        $this->_order = $this->apiRequest('Orders', (string) $this->_xml->Token);
        $this->_client = $this->apiRequest('Clients', (string) $this->_order->OrderInfo->ClientId);
    }

    function apiRequest($table, $value)
    {
        $url = self::APIURL . $this->getPlugin()->getConfig('merchant_id') . "/" . $table . "/" . $value;
        try
        {
            $r = new Am_HttpRequest($url, Am_HttpRequest::METHOD_POST);
            $r->setBody("<Request><Key>" . $this->getPlugin()->getConfig('key') . "</Key></Request>");

            $resp = $r->send()->getBody();
            $xml = simplexml_load_string($resp);
            if (!$xml)
                throw new Am_Exception_Paysystem_TransactionInvalid('1SC API response is not a valid XML:' . $resp);

            if ((string) $xml->attributes()->success != 'true')
                throw new Am_Exception_Paysystem_TransactionInvalid('1SC API response is not sucessfull: ' . $resp);
        }
        catch (Exception $e)
        {
            Am_Di::getInstance()->logger->error("Error in 1sc plugin", ["exception" => $e]);
            return null;
        }
        return $xml;
    }

    public function autoCreateGetProducts()
    {
        $products = [];
        foreach ($this->_order->OrderInfo->LineItems->LineItemInfo as $l)
        {
            if ($pid = (string) $l->ProductId)
                $pl = Am_Di::getInstance()->billingPlanTable->findFirstByData('1shoppingcart_id', $pid);
            if (!$pl)
                continue;
            $p = $pl->getProduct();
            if ($p)
                $products[] = $p;
        }
        return $products;
    }

    public function findInvoiceId()
    {
        return $this->generateInvoiceExternalId();
    }

    public function fetchUserInfo()
    {
        $countryRecord = Am_Di::getInstance()->CountryTable->findFirstBy([
                    'title' => (string) $this->_client->ClientInfo->CountryName
        ]);

        if ($countryRecord && $countryRecord->isLoaded()) {
            $country = $countryRecord->country;
            $stateRecord = Am_Di::getInstance()->StateTable->findFirstBy(
                [
                    'title' => (string) $this->_client->ClientInfo->StateName,
                    'country' => $countryRecord->country
                ]);

            if ($stateRecord && $stateRecord->isLoaded())
                $state = $stateRecord->state;


        } else {
            $country = $state = '';
        }
        return [
            'name_f' => (string) $this->_client->ClientInfo->FirstName,
            'name_l' => (string) $this->_client->ClientInfo->LastName,
            'street' => (string) $this->_client->ClientInfo->Address1,
            'city' => (string) $this->_client->ClientInfo->City,
            'zip' => (string) $this->_client->ClientInfo->Zip,
            'email' => (string) $this->_client->ClientInfo->Email,
            'country' => $country,
            'state' => $state,
            'phone' => (string) $this->_client->ClientInfo->Phone
        ];
    }

    function generateUserExternalId(array $userInfo)
    {
        return (string) $this->_client->ClientInfo->Id;
    }

    public function generateInvoiceExternalId()
    {
        $roid = (string) $this->_order->OrderInfo->RecurringOrderId;
        return ($roid ? $roid : $this->getUniqId());
    }

    function validateSource()
    {
        if(!((bool) $this->_order)) return false;
        if($this->getPlugin()->findOrder($order_id = $this->getUniqId()))
            throw new Am_Exception_Paysystem_TransactionAlreadyHandled('Transaction '.$order_id.' is already handled');
        return true;
    }

    public function getUniqId()
    {
        return (string) $this->_order->OrderInfo->Id;
    }

    public function validateStatus()
    {
        return (((string) $this->_order->OrderInfo->OrderStatusType) == 'Accepted');
    }

    public function validateTerms()
    {
        return true;
    }

    public function processValidated()
    {
        if(floatval($this->_order->OrderInfo->GrandTotal) == 0)
            $this->invoice->addAccessPeriod($this);
        else
            $this->invoice->addPayment($this);
    }

    function setInvoiceLog(InvoiceLog $log)
    {
        parent::setInvoiceLog($log);
        $this->getPlugin()->logOther('1SC API NOTIFICATION', $this->_xml);
        $this->getPlugin()->logOther('1SC ORDER', $this->_order->asXML());
        $this->getPlugin()->logOther('1SC CLIENT', $this->_client->asXML());
    }
}

class Am_Paysystem_Transaction_1shoppingcart extends Am_Paysystem_Transaction_Incoming
{
    const START_RECURRING = 'start_recurring';
    const REBILL = 'rebill';
    const PAYMENT = 'payment';
    const RECURRING_EOT = 'recurring_eot';

    function validateSource()
    {
        $vars = $this->request->getPost();
        if(empty($vars['VerifySign']))
            return false;
        $sign = $vars['VerifySign'];
        unset($vars['VerifySign']);
        $vars['PostbackPassword'] = $this->plugin->getConfig('password');
        $str = join('', array_values($vars));
        $md5 = md5($str);

        if ($md5 != $sign)
        {
            throw new Am_Exception_Paysystem_TransactionInvalid("Verify sign incorrect.");
        }

        if($this->getPlugin()->findOrder($order_id = $this->getUniqId()))
            throw new Am_Exception_Paysystem_TransactionAlreadyHandled('Transaction '.$order_id.' is already handled');

        return true;
    }

    function getUniqId()
    {
        return $this->request->get('OrderID');
    }

    public function getAmount()
    {
        return $this->request->get('Amount');
    }

    function validateTerms()
    {

        if ($this->getPlugin()->getConfig('skip_amount_check'))
            return true;
        $amount = floatval($this->request->get("Amount"));
        $type = $this->request->get("Status");
        if ($type == self::RECURRING_EOT)
            return true;
        return ($amount == floatval(($type == self::REBILL ? $this->invoice->second_total : $this->invoice->first_total)));
    }

    function findInvoiceId()
    {
        return $this->request->get("AMemberID");
    }

    function loadInvoice($invoiceId)
    {
        $invoiceId = preg_replace('/-.*/', '', $invoiceId);

         // Assuming this is invoice imported from v3.
        $importedInvoiceId = Am_Di::getInstance()->db->selectCell("
            SELECT p.invoice_id
            FROM ?_invoice_payment p LEFT  JOIN ?_data d
            ON d.`table`='invoice_payment' AND d.id = p.invoice_payment_id AND d.`key` = 'am3:id'
            WHERE d.value=?", $invoiceId);

        if($importedInvoiceId)
            $invoice = Am_Di::getInstance()->invoiceTable->load($importedInvoiceId);

        if(!$invoice)
        {
            $invoice = Am_Di::getInstance()->invoiceTable->load($invoiceId);
            if($invoice->data()->get('am3:id')) $invoice = null; // Imported record. Skip it.

        }
        // update invoice_id in the log record
        if ($invoice && $this->log)
        {
            $this->log->updateQuick([
                'invoice_id' => $invoice->pk(),
                'user_id' => $invoice->user_id,
            ]);
        }
        return $invoice;
    }

    function processValidated()
    {
        switch ($this->request->get("Status"))
        {
            case self::START_RECURRING :
                if(!count($this->invoice->getAccessRecords()) && (floatval($this->invoice->first_total) == 0))
                    $this->invoice->addAccessPeriod($this);
                else
                    $this->invoice->addPayment($this);
                break;
            case self::PAYMENT :
            case self::REBILL :
                $this->invoice->addPayment($this);
                break;
            case self::RECURRING_EOT :
                $this->invoice->stopAccess($this);
                break;
        }
    }

    public function validateStatus()
    {
        return true;
    }
}